package org.sauceggplant.item.man.service.impl;

import org.sauceggplant.item.man.service.UuidService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 推特的雪花算法<br>
 * <br>
 * Twitter_Snowflake结构如下：<br>
 * 0 - 00000000 00000000 00000000 00000000 00000000 0 - 00000 - 00000 - 00000000 0000 <br>
 * 1-1:第一段1bit，是标识位，不用，因为二进制中最高位是符号位，1标识负数，0标识正数。生成的id一般都是用正数，所以最高位固定为0<br>
 * 2-41:第二段为41bit，毫秒级。可以标识的数值范围是(2^41-1)，转换成单位年则是69年。注意，41位时间戳不是存储当前时间的时间戳，
 * 而是存储时间戳的差值（当前时间戳-开始时间戳）得到的值，这里的开始时间戳，一板是我们的id生成器开始使用的时间，由我们程序来指定的<br>
 * 3-10:第三段为10bit，其中5bit表示机房位(datacenterId)，支持2^5=32个机房，5bit表示机器位(workerId)，每个机房支持2^5=32台机器，
 * 总可以部署1024台机器<br>
 * 4-12：第5段为12bit，毫秒内的序列计数，12bit支持2^12=4096个id序号（同一机房，同一机器）<br>
 * 加起来刚好64位，为一个long型<br>
 * Snowflake的优点是：64bit，纯数字，整体上按照时间自增排序，并且整个分布式系统内不会产生id碰撞，并且效率较高。<br>
 * Snowflake的缺点是：时钟回拨，服务器时钟回拨时可能会生成重复id。算法中可通过记录最后一个生成 id时的时间戳来解决，
 * 每次生成id之前比较当前服务器时钟是否被回拨，避免生成重复id。
 */
public class UuidServiceImpl implements UuidService {

    private static final Logger logger = LoggerFactory.getLogger(UuidServiceImpl.class);

    /**
     * 机房(数据中心)id
     */
    private static final long datacenterId = 0L;

    /**
     * 机器id
     */
    private static final long workerId = 0L;

    /**
     * 初始时间戳
     * 2023-03-24 00:00:00 000
     * FIXME 部署时，建议调整为第一次上线的时间
     */
    public static final long START_TIME_STAMP = 1679587200000L;

    /**
     * 时间戳的位数
     */
    public static final long TIMESTAMP_BITS = 41L;

    /**
     * 支持最大机房id所占位数
     */
    public static final long DATACENTER_ID_BITS = 5L;

    /**
     * 支持最大机器id所占位数
     */
    public static final long WORKER_ID_BITS = 5L;

    /**
     * 支持毫秒内最大序列id所占位数
     */
    public static final long SEQUENCE_BITS = 12L;

    /**
     * 机器ID左移位数
     */
    private static final long WORKER_ID_SHIFT = SEQUENCE_BITS;

    /**
     * 机房ID左移位数
     */
    private static final long DATACENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;

    /**
     * 时间戳左移位数
     */
    private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATACENTER_ID_BITS;

    /**
     * 最大时间戳id
     */
    private static final long MAX_TIMESTAMP = -1L ^ (-1L << TIMESTAMP_BITS);

    /**
     * 支持的最大机房id
     */
    private static final long MAX_DATACENTER_ID = -1L ^ (-1L << DATACENTER_ID_BITS);

    /**
     * 支持的最大机器id
     */
    private static final long MAX_WORKER_ID = -1L ^ (-1L << WORKER_ID_BITS);

    /**
     * 生成序列的掩码
     */
    private static final long SEQUENCE_MASK = -1L ^ (-1L << SEQUENCE_BITS);

    /**
     * 最后一次产生的时间戳的毫秒数
     */
    private static long lastTimestamp = -1L;

    /**
     * 毫秒内序列(0~4095)
     */
    private static long sequence = 0L;

    public UuidServiceImpl() {
        init(datacenterId, workerId);
    }

    /**
     * 初始化
     *
     * @param datacenterId 机房id
     * @param workerId     机器id
     */
    private void init(long datacenterId, long workerId) {
        if (datacenterId > MAX_DATACENTER_ID || datacenterId < 0) {
            logger.error("datacenterId can't be greater than {} or less then 0", MAX_DATACENTER_ID);
            throw new IllegalArgumentException(
                    String.format("datacenterId can't be greater than %d or less then 0", MAX_DATACENTER_ID));
        }
        if (workerId > MAX_WORKER_ID || workerId < 0) {
            logger.error("workerId can't be greater than {} or less then 0", MAX_WORKER_ID);
            throw new IllegalArgumentException(
                    String.format("workerId can't be greater than %d or less then 0", MAX_WORKER_ID));
        }
    }

    @Override
    public synchronized long generateUuid() {
        //获取当前服务器时间戳
        long timestamp = timeGen();

        //获取当前时间戳如果小于上次时间戳，则表示时间戳获取出现异常
        if (timestamp < lastTimestamp) {
            logger.error("Clock moved backwards. Refusing to generate uuid for %d milliseconds");
            throw new RuntimeException(
                    String.format("Clock moved backwards. Refusing to generate uuid for %d milliseconds",
                            lastTimestamp - timestamp));
        }

        //如果是同一毫秒内生成的，则进行毫秒内序列
        if (timestamp == lastTimestamp) {
            //序列自增
            //防止序列超过最大值，4095，所以要与SEQUENCE_MASK 按位求与
            //即如果此时sequence等于4095，加1后为4096，再和4095按位求与后，结果为0
            sequence = (sequence + 1) & SEQUENCE_MASK;
            //当某一毫秒内的时间，产生的id数量超过4096个，系统会进入等待，直到下一毫秒，系统继续产生id
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            //时间戳不是同一毫秒，毫秒内序列重置
            sequence = 0L;
        }

        //上次生成id的时间戳，把当前时间戳赋值给上次生成id的时间戳，以便下一次判断是否处在同一个毫秒内
        lastTimestamp = timestamp;

        //移位并通过或运算拼到一起组成64位的ID
        return ((timestamp - START_TIME_STAMP) << TIMESTAMP_SHIFT)
                | (datacenterId << DATACENTER_ID_SHIFT)
                | (workerId << WORKER_ID_SHIFT)
                | sequence;
    }

    /**
     * 获取当前服务器时间戳
     *
     * @return 当前服务器时间戳
     */
    private long timeGen() {
        return System.currentTimeMillis();
    }

    /**
     * 阻塞到下一个毫秒，直到获得新的时间戳
     *
     * @param lastTimestamp 上次生成id的时间戳
     * @return 当前时间戳
     */
    private long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }
}